package com.hfi.ood.litejunit.v2.framework;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * @author ChangLiang
 * @date 2020/5/26
 */
public class TestSuite extends Assert implements Test {

    private List<Test> tests = new ArrayList<>();

    private String name;

    public TestSuite(String name) {
        this.name = name;
    }

    public TestSuite(final Class<?> theClass) {
        this.name = theClass.getName();
        Constructor<?> constructor = null;
        try {
            constructor = getConstructor(theClass);
        } catch (NoSuchMethodException e) {
            // treat Class not has public constructor condition as fail
            addTest(this.warning("Class " + theClass.getName() + " has no public constructor TestCase(String name)"));
            return;
        }

        if (!Modifier.isPublic(theClass.getModifiers())) {
            addTest(this.warning("Class " + theClass.getName() + " is not public"));
            return;
        }

        Vector<String> names = new Vector<>();
        Method[] methods = theClass.getDeclaredMethods();
        for (Method method : methods) {
            addTestMethod(method, names, constructor);
        }
        if (tests.size() == 0) {
            addTest(warning("No tests found in " + theClass.getName()));
        }
    }

    private void addTestMethod(Method method, Vector<String> names, Constructor<?> constructor) {
        String name = method.getName();
        if (names.contains(name)) {
            return;
        }
        if (isPublicTestMethod(method)) {
            names.addElement(name);

            Object[] args = new Object[]{name};
            try {
                addTest((Test) constructor.newInstance(args));
            } catch (InstantiationException e) {
                addTest(warning("Cannot instantiate test case: " + name + " (" + exceptionToString(e) + ")"));
            } catch (IllegalAccessException e) {
                addTest(warning("Cannot access test case: " + name + " (" + exceptionToString(e) + ")"));
            } catch (InvocationTargetException e) {
                addTest(warning("Exception in constructor: " + name + " (" + exceptionToString(e.getTargetException()) + ")"));
            }
        } else {
            if (isTestMethod(method)) {
                addTest(warning("Test method isn't public " + method.getName()));
            }
        }
    }

    private String exceptionToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        throwable.printStackTrace(writer);
        return stringWriter.toString();
    }

    private boolean isPublicTestMethod(Method method) {
        return isTestMethod(method) && Modifier.isPublic(method.getModifiers());
    }

    private boolean isTestMethod(Method method) {
        String name = method.getName();
        Class<?>[] parameters = method.getParameterTypes();
        Class<?> returnType = method.getReturnType();
        return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
    }

    private Constructor<?> getConstructor(Class<?> theClass) throws NoSuchMethodException {
        Class<?>[] args = {String.class};
        return theClass.getConstructor(args);
    }

    @Override
    public int countTestCases() {
        int count = 0;
        for (Iterator<Test> iterator = tests(); iterator.hasNext(); ) {
            Test test = iterator.next();
            count += test.countTestCases();
        }
        return count;
    }

    @Override
    public void run(TestResult testResult) {
        for (Iterator<Test> iterator = tests(); iterator.hasNext(); ) {
            if (testResult.shouldStop()) {
                break;
            }
            Test test = iterator.next();
            test.run(testResult);
        }
    }



    private Iterator<Test> tests() {
        return tests.iterator();
    }

    private Test warning(final String message) {
        return new TestCase("warning") {

            @Override
            public void runTest() {
                fail(message);
            }
        };
    }

    /**
     * 对于TestSuite -> TestSuite -> ... -> TestSuite的情况
     * 使用此方法 组合模式
     * @param test
     */
    public void addTest(Test test) {
        tests.add(test);
    }

    /**
     * 实际运行过程中 就是一级TestSuite->TestCase
     * 对于TestSuite -> TestSuite -> ... -> TestCase的情况
     * 并不使用此方法
     * @param testClass
     */
    public void addTestSuite(Class testClass) {
        addTest(new TestSuite(testClass));
    }


}
